<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>一、Keep-alive 是什么</h2>
  <div>keep-alive是vue中的内置组件，能在组件切换过程中将状态保留在内存中，防止重复渲染DOM</div>
  <br>
  <div>keep-alive 包裹动态组件时，会缓存不活动的组件实例，而不是销毁它们</div>
  <br>
  <div>keep-alive可以设置以下props属性：</div>
  <ul>
    <li>include - 字符串或正则表达式。只有名称匹配的组件会被缓存</li>
    <li>exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存</li>
    <li>max - 数字。最多可以缓存多少组件实例</li>
  </ul>
  <script>
    // 关于keep-alive的基本用法：
    // <keep-alive>
    //   <component :is="view"></component>
    // </keep-alive>

    // 使用includes和exclude：
    // <keep-alive include="a,b">
    //   <component :is="view"></component>
    // </keep-alive>

    // <!-- 正则表达式 (使用 `v-bind`) -->
    // <keep-alive :include="/a|b/">
    //   <component :is="view"></component>
    // </keep-alive>

    // <!-- 数组 (使用 `v-bind`) -->
    // <keep-alive :include="['a', 'b']">
    //   <component :is="view"></component>
    // </keep-alive>
  </script>
  <br>
  <div>匹配首先检查组件自身的 name 选项，如果 name 选项不可用，则匹配它的局部注册名称 (父组件 components 选项的键值)，匿名组件不能被匹配</div>
  <br>
  <div>设置了 keep-alive 缓存的组件，会多出两个生命周期钩子（activated与deactivated）：</div>
  <ul>
    <li>首次进入组件时：beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated</li>
    <li>再次进入组件时：beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated</li>
  </ul>
  <h2>二、使用场景</h2>
  <div>使用原则：当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive</div>
  <br>
  <div>当我们从首页–>列表页–>商详页–>再返回，这时候列表页应该是需要keep-alive</div>
  <br>
  <div>从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存)，这时候可以按需来控制页面的keep-alive</div>
  <br>
  <div>在路由中设置keepAlive属性判断是否需要缓存</div>
  <script>
    // {
    //   path: 'list',
    //   name: 'itemList', // 列表页
    //   component (resolve) {
    //     require(['@/pages/item/list'], resolve)
    // },
    // meta: {
    //   keepAlive: true,
    //   title: '列表页'
    // }
    // }

    // 使用<keep-alive>
      // <div id="app" class='wrapper'>
      //     <keep-alive>
      //         <!-- 需要缓存的视图组件 --> 
      //         <router-view v-if="$route.meta.keepAlive"></router-view>
      //     </keep-alive>
      //       <!-- 不需要缓存的视图组件 -->
      //     <router-view v-if="!$route.meta.keepAlive"></router-view>
      // </div>

      // export default {
      //   name: 'keep-alive',
      //   abstract: true,

      //   props: {
      //     include: [String, RegExp, Array],
      //     exclude: [String, RegExp, Array],
      //     max: [String, Number]
      //   },

      //   created () {
      //     this.cache = Object.create(null)
      //     this.keys = []
      //   },

      //   destroyed () {
      //     for (const key in this.cache) {
      //       pruneCacheEntry(this.cache, key, this.keys)
      //     }
      //   },

      //   mounted () {
      //     this.$watch('include', val => {
      //       pruneCache(this, name => matches(val, name))
      //     })
      //     this.$watch('exclude', val => {
      //       pruneCache(this, name => !matches(val, name))
      //     })
      //   },

      //   render() {
      //     /* 获取默认插槽中的第一个组件节点 */
      //     const slot = this.$slots.default
      //     const vnode = getFirstComponentChild(slot)
      //     /* 获取该组件节点的componentOptions */
      //     const componentOptions = vnode && vnode.componentOptions

      //     if (componentOptions) {
      //       /* 获取该组件节点的名称，优先获取组件的name字段，如果name不存在则获取组件的tag */
      //       const name = getComponentName(componentOptions)

      //       const { include, exclude } = this
      //       /* 如果name不在inlcude中或者存在于exlude中则表示不缓存，直接返回vnode */
      //       if (
      //         (include && (!name || !matches(include, name))) ||
      //         // excluded
      //         (exclude && name && matches(exclude, name))
      //       ) {
      //         return vnode
      //       }

      //       const { cache, keys } = this
      //       /* 获取组件的key值 */
      //       const key = vnode.key == null
      //         // same constructor may get registered as different local components
      //         // so cid alone is not enough (#3269)
      //         ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
      //         : vnode.key
      //     /*  拿到key值后去this.cache对象中去寻找是否有该值，如果有则表示该组件有缓存，即命中缓存 */
      //       if (cache[key]) {
      //         vnode.componentInstance = cache[key].componentInstance
      //         // make current key freshest
      //         remove(keys, key)
      //         keys.push(key)
      //       }
      //         /* 如果没有命中缓存，则将其设置进缓存 */
      //         else {
      //         cache[key] = vnode
      //         keys.push(key)
      //         // prune oldest entry
      //         /* 如果配置了max并且缓存的长度超过了this.max，则从缓存中删除第一个 */
      //         if (this.max && keys.length > parseInt(this.max)) {
      //           pruneCacheEntry(cache, keys[0], keys, this._vnode)
      //         }
      //       }

      //       vnode.data.keepAlive = true
      //     }
      //     return vnode || (slot && slot[0])
      //   }
      // }
  </script>
  <h2>三、原理分析</h2>
  <div>keep-alive是vue中内置的一个组件</div>
  <br>
  <div>可以看到该组件没有template，而是用了render，在组件渲染的时候会自动执行render函数</div>
  <script>
    // this.cache是一个对象，用来存储需要缓存的组件，它将以如下形式存储：
    // this.cache = {
    //     'key1':'组件1',
    //     'key2':'组件2',
    //     // ...
    // }

    // 在组件销毁的时候执行pruneCacheEntry函数
    // function pruneCacheEntry (
    //   cache: VNodeCache,
    //   key: string,
    //   keys: Array<string>,
    //   current?: VNode
    // ) {
    //   const cached = cache[key]
    //   /* 判断当前没有处于被渲染状态的组件，将其销毁*/
    //   if (cached && (!current || cached.tag !== current.tag)) {
    //     cached.componentInstance.$destroy()
    //   }
    //   cache[key] = null
    //   remove(keys, key)
    // }

    // 在mounted钩子函数中观测 include 和 exclude 的变化，如下：
    // mounted () {
    //     this.$watch('include', val => {
    //         pruneCache(this, name => matches(val, name))
    //     })
    //     this.$watch('exclude', val => {
    //         pruneCache(this, name => !matches(val, name))
    //     })
    // }

    // 如果include 或exclude 发生了变化，即表示定义需要缓存的组件的规则或者不需要缓存的组件的规则发生了变化，那么就执行pruneCache函数，函数如下：
    // function pruneCache (keepAliveInstance, filter) {
    //   const { cache, keys, _vnode } = keepAliveInstance
    //   for (const key in cache) {
    //     const cachedNode = cache[key]
    //     if (cachedNode) {
    //       const name = getComponentName(cachedNode.componentOptions)
    //       if (name && !filter(name)) {
    //         pruneCacheEntry(cache, key, keys, _vnode)
    //       }
    //     }
    //   }
    // }
  </script>
  <h2>四、思考题：缓存后如何获取数据</h2>
  <div>解决方案可以有以下两种：</div>
  <ul>
    <li>beforeRouteEnter</li>
    <li>actived</li>
  </ul>
  <h4>beforeRouteEnter</h4>
  <div>每次组件渲染的时候，都会执行beforeRouteEnter</div>
  <h4>actived</h4>
  <div>在keep-alive缓存的组件被激活的时候，都会执行actived钩子</div>
  <br>
  <div>注意：服务器端渲染期间avtived不被调用</div>
  <script>
    // 每次组件渲染的时候，都会执行beforeRouteEnter
    // beforeRouteEnter(to, from, next){
    //     next(vm=>{
    //         console.log(vm)
    //         // 每次进入路由执行
    //         vm.getData()  // 获取数据
    //     })
    // },

    // 在keep-alive缓存的组件被激活的时候，都会执行actived钩子
    // activated(){
    //   this.getData() // 获取数据
    // },
  </script>
</body>
</html>